// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

/// A type that represents a remote multimodal model (like Gemini), with the ability to generate
/// content based on various input types.
///
/// **Public Preview**: This API is a public preview and may be subject to change.
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public final class TemplateGenerativeModel: Sendable {
  let generativeAIService: GenerativeAIService
  let apiConfig: APIConfig

  init(generativeAIService: GenerativeAIService, apiConfig: APIConfig) {
    self.generativeAIService = generativeAIService
    self.apiConfig = apiConfig
  }

  /// Generates content from a prompt template and inputs.
  ///
  /// **Public Preview**: This API is a public preview and may be subject to change.
  ///
  /// - Parameters:
  ///   - templateID: The ID of the prompt template to use.
  ///   - inputs: A dictionary of variables to substitute into the template.
  ///   - options: The ``RequestOptions`` for the request, currently used to override default
  /// request timeout.
  /// - Returns: The content generated by the model.
  /// - Throws: A ``GenerateContentError`` if the request failed.
  public func generateContent(templateID: String,
                              inputs: [String: Any],
                              options: RequestOptions = RequestOptions()) async throws
    -> GenerateContentResponse {
    let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
    return try await generateContentWithHistory(
      history: [],
      template: templateID,
      inputs: templateInputs,
      options: options
    )
  }

  /// Generates content from a prompt template, inputs, and history.
  ///
  /// - Parameters:
  ///   - history: The conversation history to use.
  ///   - template: The prompt template to use.
  ///   - inputs: A dictionary of variables to substitute into the template.
  /// - Returns: The content generated by the model.
  /// - Throws: A ``GenerateContentError`` if the request failed.
  func generateContentWithHistory(history: [ModelContent], template: String,
                                  inputs: [String: TemplateInput],
                                  options: RequestOptions = RequestOptions()) async throws
    -> GenerateContentResponse {
    let request = TemplateGenerateContentRequest(
      template: template,
      inputs: inputs,
      history: history,
      projectID: generativeAIService.firebaseInfo.projectID,
      stream: false,
      apiConfig: apiConfig,
      options: options
    )
    let response: GenerateContentResponse = try await generativeAIService
      .loadRequest(request: request)
    return response
  }

  /// Generates content from a prompt template and inputs, with streaming responses.
  ///
  /// **Public Preview**: This API is a public preview and may be subject to change.
  ///
  /// - Parameters:
  ///   - templateID: The ID of the prompt template to use.
  ///   - inputs: A dictionary of variables to substitute into the template.
  ///   - options: The ``RequestOptions`` for the request, currently used to override default
  /// request timeout.
  /// - Returns: An `AsyncThrowingStream` that yields `GenerateContentResponse` objects.
  /// - Throws: A ``GenerateContentError`` if the request failed.
  public func generateContentStream(templateID: String,
                                    inputs: [String: Any],
                                    options: RequestOptions = RequestOptions()) throws
    -> AsyncThrowingStream<GenerateContentResponse, Error> {
    let templateInputs = try inputs.mapValues { try TemplateInput(value: $0) }
    let request = TemplateGenerateContentRequest(
      template: templateID,
      inputs: templateInputs,
      history: [],
      projectID: generativeAIService.firebaseInfo.projectID,
      stream: true,
      apiConfig: apiConfig,
      options: options
    )
    return generativeAIService.loadRequestStream(request: request)
  }

  func generateContentStreamWithHistory(history: [ModelContent], template: String,
                                        inputs: [String: TemplateInput],
                                        options: RequestOptions = RequestOptions()) throws
    -> AsyncThrowingStream<GenerateContentResponse, Error> {
    let request = TemplateGenerateContentRequest(
      template: template,
      inputs: inputs,
      history: history,
      projectID: generativeAIService.firebaseInfo.projectID,
      stream: true,
      apiConfig: apiConfig,
      options: options
    )
    return generativeAIService.loadRequestStream(request: request)
  }

  // TODO: Restore `public` determined to be releaseable along with the contents of TemplateChatSession.

  /// Creates a new chat conversation using this model with the provided history and template.
  ///
  /// - Parameters:
  ///   - templateID: The ID of the prompt template to use.
  ///   - history: The conversation history to use.
  /// - Returns: A new ``TemplateChatSession`` instance.
  func startChat(templateID: String,
                 history: [ModelContent] = []) -> TemplateChatSession {
    return TemplateChatSession(
      model: self,
      templateID: templateID,
      history: history
    )
  }
}
